classdef (CaseInsensitiveProperties = true) spectra < handleplus
    
    properties (SetObservable = true)
        data
        constants
    end
    
    properties (Hidden = true)
        log
    end
    
    methods
        function obj = spectra(varargin)
            
            if nargin == 0
                addListeners(obj)
                return;
            end
            
            if nargin == 1
                firstArg = varargin{1};
                if isa(firstArg, 'spectra') %  It is used when objects are passed as arguments.
                    obj = firstArg.copy;
                    return;
                end
                
                if isa(firstArg, 'struct')
                    obj = spectra();
                    fNames = properties(obj);
                    for ii = 1:length(fNames)
                        obj.(fNames{ii}) = firstArg.(fNames{ii});
                    end
                    return;
                end
                
            end
            
            if nargin == 3
                x = varargin{1};
                y = varargin{2};
                e = varargin{3};
                addData(obj,x,y,e);
                addListeners(obj)
                return
            end
            
        end
        
        function d = get(obj,varargin)
            f = datastruct;
            j = 1;
            d = cell(length(varargin),1);
            for i = 1:length(varargin)
                if any(strcmp(varargin{i},f.mainfield))
                    d{j} = obj.(varargin{i});
                    j    = j+1;
                end
            end
            if j == 2
                d = d{1};
            end
        end
        
        function obj = plus(varargin)
            for i = 1:nargin
                if isa(varargin{i},'spectra')
                    objS = varargin{i};
                elseif isnumeric(varargin{i})
                    b = varargin{i};
                end
            end
            
            obj = spectra(objS);
            d = get(obj,'data');
            set(d,'y',get(d,'y')+b)
        end
        
        function obj = minus(varargin)
            for i = 1:nargin
                if isa(varargin{i},'spectra')
                    objS = varargin{i};
                elseif isnumeric(varargin{i})
                    b = varargin{i};
                end
            end
            
            obj = spectra(objS);
            d = get(obj,'data');
            set(d,'y',get(d,'y')-b)
        end
        
    end
    
    
    methods (Access = public)
        
        function addData(obj,varargin)
            if length(varargin) == 3
                if isempty(obj.data)
                    obj.data = sp_data(varargin{1},varargin{2},varargin{3});
                else
                    obj.data(end+1) = sp_data(varargin{1},varargin{2},varargin{3});
                end
            end
        end
        
        function removeData(obj,ind)
            obj.data(ind).delete;
            obj.data(ind) = [];
            l = length(obj.data);
            for i = 1:l
                obj.data(i).plotting.updateOptions(i,l);
                obj.data(i).fitting.plotting.updateOptions(i,l); 
            end
        end
        
        function setSpacer(obj,s)
            if length(obj.data) == length(s)
                for i = 1:length(obj.data)
                    set(obj.data(i),'identifier',s(i))
                end
            else
                error('spectra:spacerLengthMismatch',...
                    'Spacer vector must equal the number of datasets (%i). Not %i.',...
                    length(obj.data),length(s))
            end
        end
        
        function [X_col, Y_col, I_col] = getCollapsedData(obj, varargin)
            temp = get(obj.data, 'identifier');
            if length(obj.data) == length([temp{:}])
                
                x1 = cell2mat(get(obj.data, 'x'));
                y1 = reshape(repmat(cell2mat(temp), 1, length(x1)/length(obj.data))', [], 1);
                v1 = cell2mat(get(obj.data, 'y'));
                F  = scatteredInterpolant(x1, y1, v1);
                
                if isempty(varargin)
                    xi = linspace(min(x1), max(x1), length(unique(x1)));
                    yi = linspace(min(y1), max(y1), length(unique(y1)));
                else
                    if length(varargin) == 2
                        xi = varargin{1}(:)';
                        yi = varargin{2}(:)';
                    else
                        error('spectra:gridData',...
                            'In order to grid data x1 and x2 must be supplied.')
                    end
                end
                
                [X_col, Y_col] = meshgrid(xi, yi);
                I_col = F(X_col,Y_col);
            else
                error('spectra:spacerNotSet',...
                    'Spacer vector has not been set. It must equal the number of datasets (%i). Not %i.',...
                    length(obj.data),length([temp{:}]))
            end
        end
        
        function [X, Y, I] = getGriddedData(obj, xi, yi)
            [X, Y, I] = obj.getXollapsedData(xi, yi);
        end
        
        function [X, Y, I] = getBinnedData2D(obj, varargin)
            
            if length(obj.data) < 2
               warning('spectra:binDataDataLength',...
                   'There is only one dataset so 1D binning has been applied')
               [X, I] = getBinnedData1D(obj,varargin);
               Y = nan(size(X));
               return
            end
            
            
            xx = cell2mat(get(obj.data,'x'));
            yy = reshape(repmat(cell2mat(get(obj.data, 'identifier')), 1, length(xx)/length(obj.data))', [], 1);
            z  = cell2mat(get(obj.data,'y'));
            
            if isempty(varargin)
                % Create bin ranges
                % This is a fencepost problem, hence the + 1!
                y_res = ((max(yy)-min(yy))*50);
                x_res = ((max(xx)-min(xx))*50);
                xbin = linspace(min(xx), max(xx), ((max(xx)-min(xx))/x_res) + 1);
                ybin = linspace(min(yy), max(yy), ((max(yy)-min(yy))/y_res) + 1);
            elseif length(varargin) == 2
                xbin = varargin{1}(:)';
                ybin = varargin{2}(:)';
            else
                error('spectra:binDataBins',...
                            'In order to bin data x1 and x2 must be supplied.')
            end
                
            % First bin
            [~, ind] = histc(xx, xbin);
            
            [X, Y] = meshgrid(xbin, ybin);
            
            I = zeros(length(ybin), length(xbin));
            
            
            for i = reshape(unique(ind),1,[])
                [~, ind2] = histc(yy(ind == i), ybin);
                u         = unique(ind2);
                temp      = accumarray(ind2(:), z(ind == i), [], @sum);
                I(u,i)    = temp(u);
            end
            
        end
        
        function [X, I, varargout] = getBinnedData1D(obj,varargin)
                
                if isa(obj,'spectra')
                    dataE=obj.data;
                else
                    dataE=obj;
                end
            
                xx = cell2mat(get(dataE,'x'));
                z  = cell2mat(get(dataE,'y'));
            
            if isempty(varargin)
                % Create bin ranges
                % This is a fencepost problem, hence the + 1!
                x_res = ((max(xx)-min(xx))*50);
                xbin = linspace(min(xx), max(xx), ((max(xx)-min(xx))/x_res) + 1);
            elseif length(varargin) == 1
                xbin = varargin{1}(:)';
            else
                error('spectra:binDataBins',...
                            'In order to bin data x1 must be supplied.')
            end
            
            [X, ind] = histc(xx, xbin);
            I = accumarray(ind(:),z,[],@sum); 
            
            if nargout == 3
                e = cell2mat(get(dataE,'e'));
                % Simple sun of errors
                varargout{1} = accumarray(ind(:),e,[],@(x) sum(x.^2)^0.5);
            end
            
        end
        
        function binData1D(obj,varargin)
            if length(obj.data) > 1
                warning('spectra:binDataMultiple',...
                    'There are more than 1 dataset. Binning has been applied to each.')
            end
            
            for i = 1:length(obj.data)
                [X, I, E] = getBinnedData1D(obj.data(i), varargin{:});
                obj.data(i).setdata(X, I, E);
            end
        end
        
    end
    
    
    methods (Hidden = true)
        function addListeners(obj)
            addlistener(obj,'data','PostSet',@spectra.dataAdded);
        end
    end
    
    methods (Hidden = true, Static)
        function dataAdded(~, evnt)
            obj = evnt.AffectedObject;
            l = length(obj.data);
            for i = 1:l
                obj.data(i).plotting.updateOptions(i,l);
                obj.data(i).fitting.plotting.updateOptions(i,l);
            end
        end
    end
end